﻿/*
 * 
 * 参数定义
 * 1：装载 MES 配方
 * 2：稀释工步运行后操作（与备料监控逻辑相同，但回馈不同的接口）
 * 3: 备料监控（监控 DB2200 存储罐 MES 校验结果）
 * 
 */

using FeedingSystemTimedTask;
using hyjx.comm;
using hyjx.comm.Exceptions;
using hyjx.comm.Interfaces;
using Hyjx.GanFengLogger;
using Hyjx.GanFengLogger.SqliteLog;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using S7.Net;
using Serilog;
using Serilog.Events;
using System.Net.Http.Json;
using System.Text.Json;


string dbFolder = $"{Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments)}\\hyjx";
if (!Directory.Exists(dbFolder))
    Directory.CreateDirectory(dbFolder);

IConfigurationRoot config = new ConfigurationBuilder()
        .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
        .AddJsonFile("appsettings.json", optional: false)
        .Build();

string connectString = string.Empty;
IHostBuilder builder = Host.CreateDefaultBuilder();
builder.ConfigureLogging((log) =>
{
    connectString = $"Filename={dbFolder}\\{config["LogDB:Name"]};Password={config["LogDB:Password"]}";

    log.ClearProviders();
    log.AddConsole();
    log.AddGanFengSqliteLogLogger(config =>
    {
        config.ConnectionString = connectString;
    });
    //log.AddEventLog();
});

IHost host = builder.Build();

Microsoft.Extensions.Logging.ILogger iLogger = host.Services.GetService<ILogger<Program>>() ?? throw new Exception();

string SerilogOutputTemplate = "[{Timestamp:yyy/MM/dd HH:mm:ss.fff} {Level:u3}] {Message:lj} {NewLine}{Exception}";
Log.Logger = new LoggerConfiguration()
                            .Enrich.FromLogContext()
                            .MinimumLevel.Debug()
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p =>
                            p.Level == LogEventLevel.Information).
                            WriteTo.File($"{AppDomain.CurrentDomain.BaseDirectory}/Log/{DateTime.Now:yyyyMM}/Info-.txt", rollingInterval: RollingInterval.Day,
                            rollOnFileSizeLimit: true,
                            fileSizeLimitBytes: 20_000_000,
                            retainedFileTimeLimit: TimeSpan.FromDays(90),
                            shared: true,
                            outputTemplate: SerilogOutputTemplate))
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p => p.Level == LogEventLevel.Error).WriteTo.File($"{AppDomain.CurrentDomain.BaseDirectory}/Log/{DateTime.Now:yyyyMM}/Error-.txt", rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true,
                            fileSizeLimitBytes: 20_000_000,
                            retainedFileTimeLimit: TimeSpan.FromDays(90),
                            shared: true,
                            outputTemplate: SerilogOutputTemplate))
                            .CreateLogger();

const string _readPlcAddressFailed = "从配置读取 PLC 地址失败";

if (args.Length == 0)
{
    Log.Error("更新罐体数据程序，出现传入参数为空错误");
    return;
}

Log.Warning($"PLC 更新程序被调用。参数{args[0]}");


if (args[0].StartsWith("1"))// 参数 1 表示要装载 MES 配方
{
    // 参数说明：
    // 参数 1 表示要设置装载配方
    // 参数 2 表示 CPU 类型
    // 参数 3 表示 CPU IP
    // 参数 4 表示 CPU 机架
    // 参数 5 表示 CPU 插槽
    // 参数 6 表示 制胶装载配方地址
    // 参数 7 表示制胶配方号（索引）
    Log.Information("开始装载制胶机 MES 配方……");
    string[] pars = args[0].Split(',');

    IS7Protocol plc = new S7Protocol((CpuType)Convert.ToInt32(pars[1]), pars[2], Convert.ToInt16(pars[3]), Convert.ToInt16(pars[4]));

    try
    {
        string? commandAddress = pars[5]; // 制胶装载配方地址
        string? recipeNoAddress = pars[6]; // 制胶配方号（索引）
        Log.Information($"制胶装载配方地址:{commandAddress},制胶配方号（索引）{recipeNoAddress}");

        // 写入完配方后，要装载配方
        plc.Write(recipeNoAddress ?? throw new Exception(_readPlcAddressFailed), 0);// 写入配方后要设置当前配方号
        plc.Write(commandAddress ?? throw new Exception(_readPlcAddressFailed), 100);// 装载配方
        Thread.Sleep(TimeSpan.FromSeconds(5));
        plc.Write(commandAddress ?? throw new Exception(_readPlcAddressFailed), 0);// 装载配方
        Thread.Sleep(TimeSpan.FromSeconds(1));
        plc.Write(commandAddress ?? throw new Exception(_readPlcAddressFailed), 1);// 运行配方
        Thread.Sleep(TimeSpan.FromSeconds(5));
        plc.Write(commandAddress ?? throw new Exception(_readPlcAddressFailed), 0);// 运行配方

        Log.Information("装载制胶机 MES 配方完成");
        return;
    }
    catch (S7ProtocolWriteException ex)
    {
        Log.Error(ex, "更新罐体数据程序，写制胶指令值失败");
        return;
    }
    catch (Exception ex)
    {
        Log.Error(ex, "更新罐体数据程序失败");
        return;
    }
    finally
    {
        plc.Dispose();
    }
}

if (args[0].StartsWith("2"))
{
    DoFeeding(iLogger, args[0], "稀释工步上料", false);
    return;
}

if (args[0].StartsWith("3"))
{
    DoFeeding(iLogger, args[0], "上料系统定时任务监控 MES 校验", true);
    return;
}

if (args[0].StartsWith("4"))
{
    ResetMainMesValid(args[0], "MES 主粉/EC 验证");
    return;
}

static async void DoFeeding(Microsoft.Extensions.Logging.ILogger iLogger, string args, string logMessage, bool needSubmit)
{
    // 参数说明：
    // 参数 1 表示要监控 Mes 校验设置功能
    // 参数 2 表示 CPU 类型
    // 参数 3 表示 CPU IP
    // 参数 4 表示 CPU 机架
    // 参数 5 表示 CPU 插槽
    // 参数 6 表示物料 Mes 验证结果地址
    // 参数 7 表示上料地址
    // 参数 8 表示上料重量地址
    // 参数 9 表示 MES 验证通过持续分钟数
    // 参数 10 表示 WebApi 基本地址
    // 参数 11 表示设备编码
    // 参数 12 表示接口 ID
    // 参数 13 表示物料编码

    // 当 MES 的 PDA 扫物料及扫批次校验通过后，会下发 MES 校验结果 = true 指令
    // 此方法在 DB2200 存储罐 MES 校验结果为 true 时触发：
    // --------------------------------------------
    // 功能一：
    // 1、DB22201 filling 为 true
    // 2、DB22201 filling 一直为 false 并持续 mesValidKeepMinutes 分钟
    // 1、2 的任意一个条件成立，将 MES 校验结果由 true 改成 false
    // --------------------------------------------
    //功能二：
    // Filling 由 0 变 1 时取 WeightA ，上完料由 1 变 0 时取 WeightB, WeightB - WeightA = 上料重量
    // 重量上报

    Log.Information($"开始{logMessage}");
    Thread.Sleep(TimeSpan.FromSeconds(1));

    string[] pars = args.Split(',');

    string mesValidAddress = pars[5];
    string fillingAddress = pars[6];
    string weightAddress = pars[7];
    int mesValidKeepMinutes = Convert.ToInt32(pars[8]);
    string baseUrl = pars[9];
    string deviceCode = pars[10];
    int interfaceId = Convert.ToInt32(pars[11]);
    short materialCode = Convert.ToInt16(pars[12]);
    try
    {
        IS7Protocol plc = new S7Protocol((CpuType)Convert.ToInt32(pars[1]), pars[2], Convert.ToInt16(pars[3]), Convert.ToInt16(pars[4]));

        bool valid = false;
        bool goOn = true;
        DateTime startTime = DateTime.Now;
        JarWeight jarWeight = new();
        bool isMesValidSetted = false;
        string error = $"{logMessage}失败";

        while (goOn)
        {
            try
            {
                if (needSubmit)// 备料需要提交 MES 结果，但稀释流程的上料工步，则不用上报
                {
                    bool filling = plc.ReadBool(fillingAddress);
                    if (filling) // 如果开始上料
                    {
                        Log.Information($"开始上料,点位:{fillingAddress},值:{filling}");
                        if (!isMesValidSetted)
                        {
                            plc.Write(mesValidAddress, false); // filling 为 true，重置 MES 校验结果为 false
                            await Task.Delay(TimeSpan.FromMilliseconds(180));
                            valid = plc.ReadBool(mesValidAddress);
                            Log.Information($"重置MES验证点位:{mesValidAddress},值:{valid}");
                            isMesValidSetted = true;
                        }

                        if (!jarWeight.StartCalculate) // 如果开始计算标识位 == false，表示一个新的上料过程开启了，记录上料前重量
                        {
                            jarWeight.BeforeFedding = plc.ReadFloat(weightAddress);
                            jarWeight.StartCalculate = true; // 将开始计算标识位置为 true
                        }
                    }
                    else // filling 为 false，上料完成
                    {
                        if (jarWeight.StartCalculate) // 如果开始计算标识位 == true，表示一个上料过程结束，记录上料后重量
                        {
                            jarWeight.AfterFedding = plc.ReadFloat(weightAddress);
                            Log.Information(JsonSerializer.Serialize(jarWeight));
                            float weight = jarWeight.AfterFedding - jarWeight.BeforeFedding;// 拿到上料前后重量差
                            if (weight < 0)
                                weight = 0;
                            if (jarWeight.AfterFedding < 0)
                                jarWeight.AfterFedding = 0;

                            string url = $"{baseUrl}/api/StorageJar/FeedingFinish";
                            using HttpClient http = new();
                            FeedingFinishArgs arg = new() { DeviceCode = deviceCode, MaterialCode = materialCode, JarWeight = jarWeight.AfterFedding, FeedingWeight = weight };
                            LogObject log = new() { DeviceCode = deviceCode, InterfaceId = interfaceId, RequestUrl = url, RequestContent = JsonSerializer.Serialize(arg) };

                            try
                            {
                                var response = http.PostAsJsonAsync(url, arg).GetAwaiter().GetResult(); // 提交
                                log.ResponseContent = JsonSerializer.Serialize(response);

                                Log.Information(JsonSerializer.Serialize(log));
                                if (response.StatusCode == System.Net.HttpStatusCode.NotFound || response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                                {
                                    Log.Error("找不到 MES 接口或访问受限。");
                                }
                                else
                                {
                                    GFLogger logger = new(iLogger, log.DeviceCode, log.InterfaceId, log.RequestUrl, log.RequestContent, log.ResponseContent);
                                    logger.Append("访问成功");
                                    logger.CompleteWithInformation();

                                    Log.Information("上传成功");
                                    goOn = false;
                                    break;
                                }
                            }
                            catch (HttpRequestException ex)
                            {
                                Log.Error(ex, "MES 访问失败，可能网络已经断开。");
                            }
                            catch (Exception ex)
                            {
                                Log.Error(ex, "MES 访问出现错误。");
                            }
                            finally
                            {
                                jarWeight.BeforeFedding = jarWeight.AfterFedding = 0f;
                                jarWeight.StartCalculate = false; // 将开始计算标识位置为 false
                            }
                        }
                    }
                }

                if ((DateTime.Now - startTime).TotalSeconds < mesValidKeepMinutes * 60) // 如果没超时
                {
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    continue;
                }

                valid = plc.ReadBool(mesValidAddress);
                Log.Information($"MES验证点位:{mesValidAddress},值:{valid}");
                if (valid)
                {
                    plc.Write(mesValidAddress, false); // 超时则重置 MES 校验结果
                    await Task.Delay(TimeSpan.FromMilliseconds(180));
                    valid = plc.ReadBool(mesValidAddress);
                    Log.Information($"超时复位MES验证点位:{mesValidAddress},值:{valid}");
                }

                goOn = false;
                break;
            }
            catch (S7ProtocolReadException ex)
            {
                Log.Error(ex, ex.Message);
            }
            catch (S7ProtocolWriteException ex)
            {
                Log.Error(ex, ex.Message);
            }
        }
        plc.Dispose();
        Log.Information($"{logMessage}完成");
        Log.CloseAndFlush();
    }
    catch (Exception err)
    {
        Log.Error(err, err.Message);
    }
}

static void ResetMainMesValid(string args, string logMessage)
{
    // 参数说明：
    // 参数 1 表示要监控 Mes 校验设置功能
    // 参数 2 表示 CPU 类型
    // 参数 3 表示 CPU IP
    // 参数 4 表示 CPU 机架
    // 参数 5 表示 CPU 插槽
    // 参数 6 表示物料 Mes 验证结果地址
    // 参数 7 表示 MES 验证通过持续分钟数

    string[] pars = args.Split(',');

    string mesValidAddress = pars[5];
    int mesValidKeepMinutes = Convert.ToInt32(pars[6]);

    Log.Information($"开始 {logMessage} MES验证通过有效时间:{mesValidKeepMinutes} 分钟,点位:{mesValidAddress}");
    Thread.Sleep(TimeSpan.FromSeconds(1));
    try
    {
        IS7Protocol plc = new S7Protocol((CpuType)Convert.ToInt32(pars[1]), pars[2], Convert.ToInt16(pars[3]), Convert.ToInt16(pars[4]));

        bool valid = plc.ReadBool(mesValidAddress);
        Log.Information($"MES验证点位:{mesValidAddress},值:{valid}");
        if (!valid) // WebApi 写了投料 MES 校验通过才会调此程序，MES 校验不通过，什么也不做。
            return;

        bool goOn = true;
        DateTime startTime = DateTime.Now;
        string error = $"{logMessage}失败";

        while (goOn)
        {
            try
            {
                var totalSeconds = (DateTime.Now - startTime).TotalSeconds;
                if (totalSeconds < (mesValidKeepMinutes * 60)) // 如果没超时
                {
                    Log.Information($"{logMessage},MES验证点位:{mesValidAddress},累计时间:{totalSeconds}秒");
                    Thread.Sleep(TimeSpan.FromSeconds(1));
                    continue;
                }
                var oldValue = plc.ReadBool(mesValidAddress);
                plc.Write(mesValidAddress, false); // 超时则重置 MES 校验结果
                valid = plc.ReadBool(mesValidAddress);
                Log.Information($"{logMessage}完成,复位点位:{mesValidAddress},值:{oldValue}-->>{valid}");
                goOn = false;
                break;
            }
            catch (S7ProtocolReadException ex)
            {
                Log.Error(ex, ex.Message);
            }
            catch (S7ProtocolWriteException ex)
            {
                Log.Error(ex, ex.Message);
            }
        }
        plc.Dispose();
        Log.CloseAndFlush();
    }
    catch (Exception err)
    {
        Log.Error(err, err.Message);
    }
}

